package com.gitee.jmash.rbac.service;

import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.alipay.api.response.AlipayUserInfoShareResponse;
import com.gitee.jmash.common.cache.SerialCache;
import com.gitee.jmash.common.event.SafeEvent;
import com.gitee.jmash.common.excel.ExcelImport;
import com.gitee.jmash.common.grpc.GrpcContext;
import com.gitee.jmash.common.jaxrs.WebContext;
import com.gitee.jmash.common.lock.DistributedLock;
import com.gitee.jmash.common.security.DefaultJmashPrincipal;
import com.gitee.jmash.common.security.JmashPrincipal;
import com.gitee.jmash.common.utils.UUIDUtil;
import com.gitee.jmash.core.jaxrs.ParamsValidationException;
import com.gitee.jmash.core.orm.cdi.JpaTenantService;
import com.gitee.jmash.core.service.ServiceException;
import com.gitee.jmash.core.transaction.JakartaTransaction;
import com.gitee.jmash.core.utils.FieldMaskUtil;
import com.gitee.jmash.core.utils.NetSecretUtil;
import com.gitee.jmash.core.utils.TenantUtil;
import com.gitee.jmash.crypto.digests.SM3Util;
import com.gitee.jmash.file.client.cdi.FileClient;
import com.gitee.jmash.rbac.AuthProps;
import com.gitee.jmash.rbac.client.shiro.JmashJsonWebToken;
import com.gitee.jmash.rbac.client.token.OrganUserAccessToken;
import com.gitee.jmash.rbac.dao.OpensDao;
import com.gitee.jmash.rbac.dao.RolesDutyDao;
import com.gitee.jmash.rbac.entity.OpensEntity;
import com.gitee.jmash.rbac.entity.OpensEntity.OpensPk;
import com.gitee.jmash.rbac.entity.RoleEntity;
import com.gitee.jmash.rbac.entity.TokenEntity;
import com.gitee.jmash.rbac.entity.TokenEntity.TokenPk;
import com.gitee.jmash.rbac.entity.UserEntity;
import com.gitee.jmash.rbac.entity.UserSecretEntity;
import com.gitee.jmash.rbac.enums.SecretType;
import com.gitee.jmash.rbac.enums.TokenType;
import com.gitee.jmash.rbac.excel.UserHeaderImport;
import com.gitee.jmash.rbac.exception.JmashAuthenticationException;
import com.gitee.jmash.rbac.mapper.UserMapper;
import com.gitee.jmash.rbac.model.TokenAuthzCodeReq;
import com.gitee.jmash.rbac.model.UserOpenCreateReq;
import com.gitee.jmash.rbac.thirdparty.unionpay.model.UnionUserMobile;
import com.gitee.jmash.rbac.utils.PwdHashUtil;
import com.gitee.jmash.rbac.utils.TokenUtil;
import com.gitee.jmash.rbac.utils.ValidateUtil;
import com.gitee.jmash.rbac.utils.VerifyCodeUtil;
import com.gitee.jmash.storage.client.StorageClient;
import com.xyvcard.wechat.client.WechatClient;
import io.smallrye.jwt.build.Jwt;
import jakarta.enterprise.event.Event;
import jakarta.enterprise.inject.Typed;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.LockModeType;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
import jakarta.validation.ValidationException;
import jakarta.validation.executable.ValidateOnExecution;
import java.io.File;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import jmash.protobuf.Gender;
import jmash.rbac.protobuf.ChangePhoneEmailReq;
import jmash.rbac.protobuf.ChangeWechatBindReq;
import jmash.rbac.protobuf.DutyType;
import jmash.rbac.protobuf.ForgotPwdReq;
import jmash.rbac.protobuf.LoginAppReq;
import jmash.rbac.protobuf.LoginQrcodeReq;
import jmash.rbac.protobuf.LoginReq;
import jmash.rbac.protobuf.LogoutReq;
import jmash.rbac.protobuf.OpensType;
import jmash.rbac.protobuf.OrganUserCreateReq;
import jmash.rbac.protobuf.RefreshTokenReq;
import jmash.rbac.protobuf.RegisterUserReq;
import jmash.rbac.protobuf.ScanCodeCreateUserReq;
import jmash.rbac.protobuf.UpdateUserReq;
import jmash.rbac.protobuf.UserCreateReq;
import jmash.rbac.protobuf.UserEnableKey;
import jmash.rbac.protobuf.UserImportReq;
import jmash.rbac.protobuf.UserStatus;
import jmash.rbac.protobuf.UserUpdateReq;
import jmash.rbac.protobuf.ValidCodeLoginReq;
import jmash.storage.protobuf.StorageOrganUserCreateReq;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.subject.Subject;
import org.eclipse.microprofile.config.inject.ConfigProperties;
import org.eclipse.microprofile.jwt.JsonWebToken;
import xyvcard.wechat.protobuf.LoginQrcodeCheckReq;
import xyvcard.wechat.protobuf.OauthUserInfoResp;

/**
 * 用户 rbac_user写服务.
 *
 * @author <a href="mailto:service@crenjoy.com">crenjoy</a>
 */
@Typed(UserWrite.class)
@Transactional(TxType.REQUIRED)
@JpaTenantService
@ValidateOnExecution
public class UserWriteBean extends UserReadBean implements UserWrite, JakartaTransaction {

  private static Log log = LogFactory.getLog(UserWriteBean.class);
  protected OpensDao opensDao = new OpensDao(this.tem);
  protected RolesDutyDao dutyDao = new RolesDutyDao(this.tem);
  @Inject
  @ConfigProperties
  AuthProps authProps;
  /**
   * 安全日志.
   */
  @Inject
  Event<SafeEvent> event;
  @Inject
  DistributedLock lock;
  @Inject
  RoleWrite roleWrite;

  /**
   * 缓存.
   */
  public static SerialCache getCache() {
    return CDI.current().select(SerialCache.class).get();
  }

  @Override
  @PersistenceContext(unitName = "WriteRbac")
  public void setEntityManager(EntityManager entityManager) {
    this.tem.setEntityManager(entityManager, true);
  }

  /**
   * 系统内置用户校验.
   *
   * @param entity 用户
   */
  public void validSysUser(UserEntity entity) {
    if ("jmash".equals(entity.getLoginName())) {
      throw new ValidationException("系统内置用户jmash禁止修改!");
    }
  }

  /**
   * 验证.
   */
  private void validator(UserEntity account) {
    if (StringUtils.isNotBlank(account.getLoginName())) {
      UserEntity repeat = userDao.findByUserName(account.getDirectoryId(), account.getLoginName());
      if (null != repeat && !repeat.getUserId().equals(account.getUserId())) {
        throw new ParamsValidationException("loginName", "登录名已被使用");
      }
    }
    if (StringUtils.isNotBlank(account.getMobilePhone())) {
      UserEntity repeat =
          userDao.findByUserName(account.getDirectoryId(), account.getMobilePhone());
      if (null != repeat && !repeat.getUserId().equals(account.getUserId())) {
        throw new ParamsValidationException("mobilePhone", "手机号码已被使用");
      }
    }
    if (StringUtils.isNotBlank(account.getEmail())) {
      UserEntity repeat = userDao.findByUserName(account.getDirectoryId(), account.getEmail());
      if (null != repeat && !repeat.getUserId().equals(account.getUserId())) {
        throw new ParamsValidationException("email", "Email已被使用");
      }
    }
  }

  @Override
  public UserEntity insert(String unifiedId, UserCreateReq user) {
    if (StringUtils.isNotBlank(unifiedId)) {
      user = user.toBuilder().setRequestId(UUID.randomUUID().toString()).build();
    }
    UserEntity userEntity = UserMapper.INSTANCE.create(user);
    // 1.唯一性校验.
    validator(userEntity);
    if (!StringUtils.equals(user.getPwd(), user.getRepeatPwd())) {
      throw new ParamsValidationException("repeatPwd", "重复密钥不一致");
    }
    checkSsdRole(user.getRoleIdsList());
    // 2.仅校验,不执行.
    if (user.getValidateOnly()) {
      return userEntity;
    }
    // 3.检查是否重复请求.
    if (!lock.lock(user.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    userEntity.setUnifiedId(unifiedId);
    // 4.执行业务(创建人及时间内部处理.)
    userDao.persist(userEntity);

    // 设置密码.
    if (StringUtils.isNotBlank(user.getPwd())) {
      UserSecretEntity secret = new UserSecretEntity();
      secret.setUserId(userEntity.getUserId());
      secret.setSecretType(SecretType.Login.name());
      secret.setPwdAlt(UUIDUtil.uuid32(UUID.randomUUID()));
      String pwdValue =
          PwdHashUtil.encrypt(secret.getPwdFormat(), user.getPwd(), secret.getPwdAlt());
      secret.setPwdValue(pwdValue);
      userSecretDao.persist(secret);
    }
    if (!user.getCreatePlatformUser() || StringUtils.isNotBlank(unifiedId)) {
      // 角色设置.
      userRolesDao.addUserRoles(userEntity.getUserId(), user.getRoleIdsList());
      // 部门岗位设置.
      userJobsDao.addUserJobs(userEntity.getUserId(), user.getUserJobsList());
    }
    if (StringUtils.isNotBlank(unifiedId)) {
      // 添加用户到组织.
      String organId = TenantUtil.getTenantIdentifierStr(getTenant());
      StorageOrganUserCreateReq.Builder storage = StorageOrganUserCreateReq.newBuilder();
      storage.setRequestId(UUID.randomUUID().toString()).setTenant(user.getPlatformTenant());
      storage.setOrganId(organId).setUnifiedId(unifiedId);
      StorageClient.getStorageBlockingStub().createStorageOrganUser(storage.build());
    }

    return userEntity;
  }

  @Override
  public UserEntity update(UserUpdateReq req) {
    // 1.唯一性校验.
    UserEntity validEntity = new UserEntity();
    validEntity.setUserId(UUIDUtil.fromString(req.getUserId()));
    FieldMaskUtil.copyMask(validEntity, req, req.getUpdateMask());
    validator(validEntity);
    checkSsdRole(req.getRoleIdsList());
    // 2.仅校验,不执行.
    if (req.getValidateOnly()) {
      return validEntity;
    }

    // 3.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }

    // 4.执行业务
    UserEntity userEntity =
        userDao.find(UUIDUtil.fromString(req.getUserId()), req.getValidateOnly());
    if (null == userEntity) {
      throw new ValidationException("找不到实体:" + req.getUserId());
    }
    // 内置用户校验
    validSysUser(userEntity);
    // 无需更新,返回当前数据库数据.
    if (req.getUpdateMask().getPathsCount() == 0) {
      return userEntity;
    }
    // 更新掩码属性
    FieldMaskUtil.copyMask(userEntity, req, req.getUpdateMask());
    userDao.merge(userEntity);

    // 角色设置
    if (req.getRoleIdsCount() > 0
        || FieldMaskUtil.containsOneFields(req.getUpdateMask(), "roleIds")) {
      userRolesDao.updateUserRoles(userEntity.getUserId(), req.getRoleIdsList());
    }

    // 部门岗位设置
    if (req.getUserJobsCount() > 0
        || FieldMaskUtil.containsOneFields(req.getUpdateMask(), "userJobs")) {
      userJobsDao.updateUserJobs(userEntity.getUserId(), req.getUserJobsList());
    }
    return userEntity;
  }

  /**
   * 校验角色是否静态互斥.
   */
  public void checkSsdRole(List<String> roleIds) {
    Set<UUID> roleIdSet =
        roleIds.stream().map(obj -> UUIDUtil.fromString(obj)).collect(Collectors.toSet());
    dutyDao.findDutyList(DutyType.SSD).stream().forEach(duty -> {
      if (roleIdSet.contains(duty.getSrcRoleId()) && roleIdSet.contains(duty.getDescRoleId())) {
        RoleEntity srcRole = roleDao.find(duty.getSrcRoleId());
        RoleEntity descRole = roleDao.find(duty.getDescRoleId());
        throw new ValidationException(
            srcRole.getRoleName() + "和" + descRole.getRoleName() + "角色互斥");
      }
    });
  }

  @Override
  public UserEntity delete(UUID entityId) {
    UserEntity entity = userDao.removeById(entityId);
    // 内置用户校验
    validSysUser(entity);
    userRolesDao.removeByUserId(entityId);
    userJobsDao.removeByUserId(entityId);
    return entity;
  }

  @Override
  public Integer batchDelete(Set<UUID> entityIds) {
    int i = 0;
    for (UUID entityId : entityIds) {
      UserEntity entity = userDao.removeById(entityId);
      // 内置用户校验
      validSysUser(entity);
      userRolesDao.removeByUserId(entityId);
      userJobsDao.removeByUserId(entityId);
      opensDao.removeByUserId(entityId);
      i++;
    }
    return i;
  }

  @Override
  public TokenEntity login(LoginReq req) {
    try {
      // 验证用户名和密码
      UserEntity user =
          loginByUserName(req.getDirectoryId(), req.getUserName(), req.getEncodePwd());
      userDao.refresh(user, LockModeType.PESSIMISTIC_WRITE);
      loginSuccess(user);
      JsonWebToken webToken = TokenUtil.createJsonWebToken(req.getTenant(), user, user.getStorage(),
          req.getClientId(), req.getUserName(), req.getScope());
      TokenEntity tokenEntity = this.passwordToken(webToken, req.getClientId(), req.getScope());
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(req.getUserName(), req.getTenant(), true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(req.getUserName(), req.getTenant(), ex.getMessage()));
      // 用户名或密码错误.
      if (ex.getCode() == 6) {
        loginFailed(ex.getAccountId(), SecretType.Login.name());
      }
      throw ex;
    }
  }

  @Override
  public TokenEntity loginByQrcode(LoginQrcodeReq req) {
    LoginQrcodeCheckReq.Builder request =
        LoginQrcodeCheckReq.newBuilder().setTenant(req.getTenant());
    request.setAuthorizerAppid(req.getAuthorizerAppid()).setTicket(req.getTicket());
    xyvcard.wechat.protobuf.UserModel model =
        WechatClient.getWechatBlockingStub().loginQrcodeCheck(request.build());
    if (StringUtils.isBlank(model.getOpenId())) {
      return null;
    }
    TokenEntity token = this.loginByOpenId(req.getTenant(), OpensType.wechat,
        model.getAuthorizerAppid(), model.getOpenId(), model.getUnionId(), false, null);
    if (token == null) {
      token = new TokenEntity();
      token.setTokenType("Register");
    }
    return token;
  }

  @Override
  public TokenEntity loginByValidCode(ValidCodeLoginReq req) {
    try {
      // 1.判断验证码是否正确
      if (!VerifyCodeUtil.validate(req.getName(), req.getValidCode())) {
        throw new JmashAuthenticationException(6, "验证码输入不正确,请重新获取!");
      }
      UserEntity user = userDao.findByUserName(req.getDirectoryId(), req.getName());
      // 找不到用户
      if (user == null) {
        throw new JmashAuthenticationException(0, "手机号或邮箱不存在,请检查!");
      }
      if (ValidateUtil.validatePhone(req.getName())) {
        user.setPhoneApproved(true);
      } else {
        user.setEmailApproved(true);
      }
      // 验证用户名和密码
      loginSuccess(user);
      JsonWebToken webToken = TokenUtil.createJsonWebToken(req.getTenant(), user, user.getStorage(),
          req.getClientId(), req.getName(), req.getScope());
      TokenEntity tokenEntity = this.passwordToken(webToken, req.getClientId(), req.getScope());
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(req.getName(), req.getTenant(), true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(req.getName(), req.getTenant(), ex.getMessage()));
      // 用户名或密码错误.
      if (ex.getCode() == 6) {
        loginFailed(ex.getAccountId(), SecretType.Login.name());
      }
      throw ex;
    }
  }

  @Override
  public boolean logout(LogoutReq req) {
    // 清理Token.
    TokenEntity tokenEntity = clearToken(req.getClientId(), req.getAccessToken());
    if (tokenEntity != null) {
      // 登出安全日志
      event.fireAsync(SafeEvent.logout(tokenEntity.getSubject(), req.getTenant()));
    }
    return tokenEntity != null;
  }

  /**
   * use accountDao.getEntityManager() for not update update_by .
   **/
  protected void loginFailed(UUID userId, String secretType) {
    UserEntity entity = userDao.find(userId, false);
    if (null == entity) {
      return;
    }
    // 内置用户校验
    validSysUser(entity);
    if (UserStatus.disabled.equals(entity.getStatus())) {
      return;
    }
    long lastDay = LocalDateTime.now().atZone(ZoneOffset.systemDefault()).toEpochSecond()
        - authProps.getLoginFailUnlockInterval();
    // 用户被锁
    if (UserStatus.locked.equals(entity.getStatus())) {
      // 被锁2小时的自动开锁
      if (entity.getFailedTime() != null
          && (entity.getFailedTime().atZone(ZoneOffset.systemDefault()).toEpochSecond() <= lastDay)
          && entity.getFailedCount() > 0) {
        entity.setStatus(UserStatus.enabled);
        entity.setFailedTime(LocalDateTime.of(LocalDate.EPOCH, LocalTime.MAX));
        entity.setFailedCount(0);
        userDao.merge(entity);
      }
    } else if (entity.getFailedTime() != null
        && (entity.getFailedTime().atZone(ZoneOffset.systemDefault()).toEpochSecond() > lastDay)
        && entity.getFailedCount() > authProps.getLockFailCount()) {
      // 2小时内如果登录失败到一定次数锁用户
      entity.setStatus(UserStatus.locked);
      entity.setLastLockoutTime(LocalDateTime.now());
      userDao.merge(entity);
    } else if (entity.getFailedTime() != null && entity.getFailedCount() > 0) {
      // 记录失败次数
      entity.setFailedCount(entity.getFailedCount() + 1);
      userDao.merge(entity);
    } else {
      entity.setFailedTime(LocalDateTime.now());
      entity.setFailedCount(1);
      userDao.merge(entity);
    }
  }

  /**
   * use accountDao.getEntityManager() for not update update_by .
   **/
  protected void loginSuccess(UserEntity entity) {
    if (null == entity) {
      return;
    }
    entity.setLastLoginTime(LocalDateTime.now());
    entity.setFailedTime(LocalDateTime.of(LocalDate.EPOCH, LocalTime.MAX));
    entity.setFailedCount(0);
    userDao.merge(entity);
  }

  @Override
  public Integer lockUser(Set<UUID> userIds, UserStatus userStatus) {
    int i = 0;
    for (UUID userId : userIds) {
      UserEntity entity = userDao.find(userId);
      // 内置用户校验
      validSysUser(entity);
      entity.setStatus(userStatus);
      if (userStatus.equals(UserStatus.enabled)) {
        entity.setFailedCount(0);
      }
      userDao.merge(entity);
      i++;
    }
    return i;
  }

  @Override
  public Integer approvedUser(Set<UUID> userIds, boolean approved) {
    int i = 0;
    for (UUID userId : userIds) {
      UserEntity entity = userDao.find(userId, false);
      // 内置用户校验
      validSysUser(entity);
      entity.setApproved(approved);
      userDao.merge(entity);
      i++;
    }
    return i;
  }

  @Override
  public boolean enableUser(UserEnableKey userEnableKey) {
    UUID entityId = UUIDUtil.fromString(userEnableKey.getUserId());
    UserEntity entity = userDao.find(entityId, false);
    if (null == entity) {
      throw new ValidationException("找不到实体" + entityId);
    }
    // 内置用户校验
    validSysUser(entity);
    UserStatus status = entity.getStatus();
    if (status.equals(UserStatus.locked)) {
      return false;
    }
    if (userEnableKey.getEnabled()) {
      entity.setStatus(UserStatus.enabled);
    } else {
      entity.setStatus(UserStatus.disabled);
    }
    userDao.merge(entity);
    return true;
  }

  @Override
  public Integer batchEnableUser(Set<UUID> entityIds, boolean enabled) {
    int i = 0;
    for (UUID entityId : entityIds) {
      UserEntity entity = userDao.find(entityId, false);
      if (null == entity) {
        continue;
      }
      // 内置用户校验
      validSysUser(entity);
      if (entity.getStatus().equals(UserStatus.locked)) {
        continue;
      }
      if (enabled) {
        entity.setStatus(UserStatus.enabled);
      } else {
        entity.setStatus(UserStatus.disabled);
      }
      userDao.merge(entity);
      i++;
    }
    return i;
  }

  @Override
  public TokenEntity createAuthzCode(TokenAuthzCodeReq req) {
    TokenEntity tokenEntity = findTokenById(new TokenPk(req.getUserId(), req.getClientId()));
    boolean create = (null == tokenEntity);
    if (null == tokenEntity) {
      tokenEntity = new TokenEntity();
    }
    tokenEntity.setUserId(req.getUserId());
    tokenEntity.setClientId(req.getClientId());
    tokenEntity.setUnifiedId(req.getUnifiedId());
    tokenEntity.setStorage(req.getStorage());
    tokenEntity.setRedirectUri(req.getRedirectUri());
    tokenEntity.setScope(req.getScope());
    tokenEntity.setIssuer(req.getIssuer());
    tokenEntity.setSubject(req.getSubject());
    String authorizationCode = SM3Util.get().digest(UUIDUtil.uuid32(UUID.randomUUID()));
    tokenEntity.setAuthorizationCode(authorizationCode);
    int codeExpiresIn = TokenUtil.getAuthProps().getExpiresIn();
    tokenEntity.setCodeExpireTime(LocalDateTime.now().plusSeconds(codeExpiresIn));
    tokenEntity.setAccessToken("");
    tokenEntity.setRefreshToken("");
    if (create) {
      tokenDao.persist(tokenEntity);
    } else {
      tokenDao.merge(tokenEntity);
    }
    return tokenEntity;
  }

  @Override
  public TokenEntity implicitToken(TokenAuthzCodeReq req) {
    TokenEntity tokenEntity = findTokenById(new TokenPk(req.getUserId(), req.getClientId()));
    boolean create = (null == tokenEntity);
    tokenEntity.setUserId(req.getUserId());
    tokenEntity.setClientId(req.getClientId());
    tokenEntity.setRedirectUri(req.getRedirectUri());
    tokenEntity.setScope(req.getScope());
    tokenEntity.setIssuer(req.getIssuer());
    tokenEntity.setSubject(req.getSubject());

    String accessToken = SM3Util.get().digest(UUIDUtil.uuid32(UUID.randomUUID()));
    tokenEntity.setAccessToken(accessToken);
    tokenEntity.setTokenType(TokenType.Bearer.name());
    int codeExpiresIn = TokenUtil.getAuthProps().getExpiresIn();
    tokenEntity.setExpireTime(LocalDateTime.now().plusSeconds(codeExpiresIn));
    tokenEntity.setExpiresIn(codeExpiresIn);
    tokenEntity.setRefreshToken("");
    if (create) {
      tokenDao.persist(tokenEntity);
    } else {
      tokenDao.merge(tokenEntity);
    }
    return tokenEntity;
  }

  @Override
  public TokenEntity authzCodeToken(String clientId, String code, String subject) {
    TokenEntity tokenEntity = tokenDao.findByAuthzCode(clientId, code);
    if (null == tokenEntity || tokenEntity.hasCodeExpire()) {
      throw new ServiceException("Not Fund Authorization Code OR Expire :" + code);
    }
    UserEntity user = findById(tokenEntity.getUserId());
    JsonWebToken webToken = TokenUtil.createJsonWebToken(this.getTenant(), user, user.getStorage(),
        tokenEntity.getClientId(), subject, tokenEntity.getScope());
    tokenEntity = updateToken(tokenEntity, webToken, true, true);
    tokenDao.merge(tokenEntity);
    return tokenEntity;
  }

  @Override
  public TokenEntity refreshToken(RefreshTokenReq req, boolean idToken) {
    TokenEntity tokenEntity = tokenDao.findByRefreshToken(req.getClientId(), req.getRefreshToken());
    if (null == tokenEntity) {
      throw new ParamsValidationException("refreshToken", "Not Fund " + req.getRefreshToken());
    }
    UserEntity user = findById(tokenEntity.getUserId());
    JsonWebToken webToken = TokenUtil.createJsonWebToken(req.getTenant(), user, user.getStorage(),
        req.getClientId(), tokenEntity.getSubject(), tokenEntity.getScope());
    tokenEntity = updateToken(tokenEntity, webToken, idToken, true);
    tokenDao.merge(tokenEntity);
    return tokenEntity;
  }

  @Override
  public TokenEntity passwordToken(JsonWebToken webToken, String clientId, String scope) {
    return saveAccessToken(webToken, clientId, scope, true);
  }

  @Override
  public TokenEntity clientToken(JsonWebToken webToken, String clientId, String scope) {
    return saveAccessToken(webToken, clientId, scope, false);
  }

  /**
   * 存储accessToken信息.
   */
  private TokenEntity saveAccessToken(JsonWebToken webToken, String clientId, String scope,
      boolean hasRefreshToken) {
    UUID userId = UUIDUtil.fromString(webToken.getName());
    TokenEntity tokenEntity = tokenDao.find(new TokenPk(userId, clientId), false);
    boolean create = (null == tokenEntity);
    if (null == tokenEntity) {
      tokenEntity = new TokenEntity();
      tokenEntity.setUserId(userId);
      tokenEntity.setClientId(clientId);
    }
    if (webToken instanceof JmashJsonWebToken) {
      JmashPrincipal principal = DefaultJmashPrincipal.create(webToken);
      tokenEntity.setUnifiedId(principal.getUnifiedId());
      tokenEntity.setStorage(principal.getStorage());
    }
    tokenEntity.setScope(scope);
    tokenEntity = updateToken(tokenEntity, webToken, hasRefreshToken);
    if (create) {
      tokenDao.persist(tokenEntity);
    } else {
      tokenDao.merge(tokenEntity);
    }
    return tokenEntity;
  }

  /**
   * 更新accessToken信息.
   */
  private TokenEntity updateToken(TokenEntity tokenEntity, JsonWebToken webToken,
      boolean hasRefreshToken) {
    return updateToken(tokenEntity, webToken, false, hasRefreshToken);
  }

  /**
   * 更新accessToken信息.
   */
  private TokenEntity updateToken(TokenEntity tokenEntity, JsonWebToken webToken, boolean idToken,
      boolean hasRefreshToken) {
    String accessToken = "";
    if (idToken) {
      accessToken = SM3Util.get().digest(UUIDUtil.uuid32(UUID.randomUUID()));
    } else {
      accessToken = Jwt.claims(webToken).innerSign().encrypt();
    }
    tokenEntity.setAccessToken(accessToken);
    tokenEntity.setSubject(webToken.getSubject());
    tokenEntity.setIssuer(webToken.getIssuer());
    tokenEntity.setTokenType(TokenType.Bearer.name());
    if (hasRefreshToken) {
      String refreshToken = SM3Util.get().digest(UUID.randomUUID().toString());
      tokenEntity.setRefreshToken(refreshToken);
    } else {
      tokenEntity.setRefreshToken("");
    }
    tokenEntity.setExpireTime(LocalDateTime.now().plusSeconds(authProps.getExpiresIn()));
    tokenEntity.setExpiresIn(authProps.getExpiresIn());
    return tokenEntity;
  }

  @Override
  public TokenEntity clearToken(String clientId, String accessToken) {
    TokenEntity tokenEntity = tokenDao.findByAccessToken(clientId, accessToken);
    if (tokenEntity != null) {
      tokenDao.remove(tokenEntity);
    }
    return tokenEntity;
  }

  @Override
  public boolean changePwd(String oldEncodePwd, String newEncodePwd) {
    JmashPrincipal principal = GrpcContext.getPrincipal();
    UserEntity userEntity = findById(principal.getNameUUID());
    if (null == userEntity) {
      throw new ValidationException("用户信息无效");
    }
    // 内置用户校验
    validSysUser(userEntity);
    UserSecretEntity secret =
        userSecretDao.findByKey(userEntity.getUserId(), SecretType.Login.name());
    if (secret == null) {
      secret = new UserSecretEntity();
      secret.setUserId(userEntity.getUserId());
      secret.setSecretType(SecretType.Login.name());
    }
    String oldPwd = NetSecretUtil.decrypt(oldEncodePwd);
    String oldPwdValue = PwdHashUtil.encrypt(secret.getPwdFormat(), oldPwd, secret.getPwdAlt());
    if (!StringUtils.equals(oldPwdValue, secret.getPwdValue())) {
      throw new ParamsValidationException("repeatPwd", "旧密码错误");
    }
    secret.setPwdAlt(UUIDUtil.uuid32(UUID.randomUUID()));
    String newPwd = NetSecretUtil.decrypt(newEncodePwd);
    String pwdValue = PwdHashUtil.encrypt(secret.getPwdFormat(), newPwd, secret.getPwdAlt());
    secret.setPwdValue(pwdValue);
    userSecretDao.merge(secret);
    return true;
  }

  @Override
  public boolean changePhoneEmail(ChangePhoneEmailReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    JmashPrincipal principal = GrpcContext.getPrincipal();
    UserEntity userEntity = findById(principal.getNameUUID());
    if (null == userEntity) {
      throw new ValidationException("用户信息无效");
    }
    // 内置用户校验
    validSysUser(userEntity);
    // 判断验证码是否正确.
    if (!VerifyCodeUtil.validate(userEntity.getMobilePhone(), req.getOldValidCode()) &&
        !VerifyCodeUtil.validate(userEntity.getEmail(), req.getOldValidCode())) {
      throw new JmashAuthenticationException(6, "旧验证码输入不正确,请重新获取!");
    }
    if (!VerifyCodeUtil.validate(req.getNewName(), req.getNewValidCode())) {
      throw new JmashAuthenticationException(6, "新验证码输入不正确,请重新获取!");
    }
    if (req.getNewName().indexOf("@") != -1) {
      userEntity.setEmail(req.getNewName());
    } else if (StringUtils.isNumeric(req.getNewName())) {
      userEntity.setMobilePhone(req.getNewName());
    } else {
      userEntity.setLoginName(req.getNewName());
    }
    userDao.persist(userEntity);
    return true;
  }

  @Override
  public boolean changeWechatBind(ChangeWechatBindReq req) {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    JmashPrincipal principal = GrpcContext.getPrincipal();
    UserEntity userEntity = findById(principal.getNameUUID());
    if (null == userEntity) {
      throw new ValidationException("用户信息无效");
    }
    // 内置用户校验
    validSysUser(userEntity);
    // 判断验证码是否正确.
    if (!VerifyCodeUtil.validate(userEntity.getMobilePhone(), req.getValidCode()) &&
        !VerifyCodeUtil.validate(userEntity.getEmail(), req.getValidCode())) {
      throw new JmashAuthenticationException(6, "验证码输入不正确,请重新获取!");
    }
    // 校验是否已经扫码.
    LoginQrcodeCheckReq.Builder request =
        LoginQrcodeCheckReq.newBuilder().setTenant(req.getTenant());
    request.setAuthorizerAppid(req.getAuthorizerAppid()).setTicket(req.getTicket());
    xyvcard.wechat.protobuf.UserModel model =
        WechatClient.getWechatBlockingStub().loginQrcodeCheck(request.build());
    if (StringUtils.isBlank(model.getOpenId())) {
      throw new JmashAuthenticationException(6, "扫码失败,请重新扫码!");
    }
    // 移除旧三方登录信息.
    opensDao.removeByUserIdAndOpenType(userEntity.getUserId(), OpensType.wechat);
    // 插入新三方登录信息.
    insertThirdLogin(model.getOpenId(), userEntity.getUserId(), req.getAuthorizerAppid(),
        model.getUnionId(), OpensType.wechat);
    return true;
  }

  /**
   * 插入三方登录信息.
   *
   * @param openId   OpenID
   * @param userId   账号ID
   * @param appId    应用ID
   * @param unionId  唯一ID
   * @param openType 三方OpenID Type
   */
  public void insertThirdLogin(String openId, UUID userId, String appId, String unionId,
      OpensType openType) {
    OpensEntity curOpen = new OpensEntity();
    curOpen.setOpenType(openType);
    curOpen.setAppId(appId);
    curOpen.setOpenId(openId);
    curOpen.setUnionId(unionId);
    curOpen.setUserId(userId);
    opensDao.merge(curOpen);
  }

  @Override
  public boolean resetPwd(UUID userId, String encodePwd, String repeatPwd) {
    if (!StringUtils.equals(encodePwd, repeatPwd)) {
      throw new ParamsValidationException("repeatPwd", "重复密钥不一致");
    }
    UserEntity userEntity = findById(userId);
    if (null == userEntity) {
      throw new ValidationException("用户不存在");
    }
    // 内置用户校验
    validSysUser(userEntity);
    UserSecretEntity secret =
        userSecretDao.findByKey(userEntity.getUserId(), SecretType.Login.name());
    if (secret == null) {
      secret = new UserSecretEntity();
      secret.setUserId(userEntity.getUserId());
      secret.setSecretType(SecretType.Login.name());
    }
    secret.setPwdAlt(UUIDUtil.uuid32(UUID.randomUUID()));
    String pwd = NetSecretUtil.decrypt(encodePwd);
    String pwdValue = PwdHashUtil.encrypt(secret.getPwdFormat(), pwd, secret.getPwdAlt());
    secret.setPwdValue(pwdValue);
    userSecretDao.merge(secret);
    return true;
  }

  @Override
  public String importUser(UserImportReq req) throws Exception {
    // 1.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    // 文件检查
    File file = FileClient.downloadFile(req.getFileNames());
    if (!file.exists()) {
      log.error("找不到文件：" + req.getFileNames());
      return "找不到文件：" + req.getFileNames();
    }

    int success = 0;
    int fail = 0;

    ExcelImport excel = new ExcelImport();
    excel.addHeaders(UserHeaderImport.getHeaderImports());
    excel.openExcel(file);
    int sheetIndex = 0;

    if (req.getAddFlag()) {
      // 新增
      List<UserCreateReq> createDetails =
          excel.importEntity(sheetIndex, UserCreateReq.getDefaultInstance());
      int i = 0;
      for (UserCreateReq createReq : createDetails) {
        try {
          insert("", createReq.toBuilder().setRequestId(req.getRequestId() + (i++)).build());
          success++;
        } catch (Exception ex) {
          log.error("", ex);
          fail++;
        }
      }
    } else {
      // 更新
      List<UserUpdateReq> updateDetails =
          excel.importEntity(sheetIndex, UserUpdateReq.getDefaultInstance());
      int i = 0;
      for (UserUpdateReq updateReq : updateDetails) {
        try {
          update(updateReq.toBuilder().setRequestId(req.getRequestId() + (i++))
              .setUpdateMask(req.getUpdateMask()).build());
          success++;
        } catch (Exception ex) {
          log.error("", ex);
          fail++;
        }
      }
    }

    return String.format("导入成功%d行,失败%d行.<br/>%s", success, fail, excel.getErrorMsg());
  }

  @Override
  public OpensEntity deleteUserOpens(OpensPk pk) {
    return opensDao.removeById(pk);
  }

  @Override
  public UserEntity updateUser(UpdateUserReq user, String userId) {
    UserEntity userEntity = findById(UUIDUtil.fromString(userId));
    if (null == userEntity) {
      throw new ValidationException("用户信息无效");
    }
    // 内置用户校验
    validSysUser(userEntity);
    /* 检验是否为重复请求 */
    if (!lock.lock(user.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    /* 若需要更新的字段数量为空则返回true */
    if (user.getUpdateMask().getPathsCount() == 0) {
      return userEntity;
    }
    FieldMaskUtil.copyMask(userEntity, user, user.getUpdateMask());
    userDao.merge(userEntity);
    return userEntity;
  }

  @Override
  public TokenEntity loginByOpenId(String tenant, OpensType openType, String appId, String openId,
      String unionId, boolean checkPhone, String phoneNumber) {
    try {
      // OpenId 登录.
      OpensEntity entity = opensDao.findByOpenId(openType, appId, openId);
      if (entity == null && StringUtils.isNotBlank(unionId)) {
        // UnionId 登录.
        entity = opensDao.findByUnionId(openType, unionId);
        if (entity != null) {
          // 记录当前应用绑定信息.
          OpensEntity curOpen = new OpensEntity();
          curOpen.setOpenType(openType);
          curOpen.setAppId(appId);
          curOpen.setOpenId(openId);
          curOpen.setUnionId(unionId);
          curOpen.setUserId(entity.getUserId());
          opensDao.merge(curOpen);
        }
      }
      // 未登录.
      if (null == entity) {
        return null;
      }
      UserEntity userEntity = userDao.find(entity.getUserId(), false);
      if (StringUtils.isNotBlank(phoneNumber)) {
        userEntity.setMobilePhone(phoneNumber);
      }
      if (checkPhone && StringUtils.isBlank(userEntity.getMobilePhone())) {
        return null;
      }
      // 用户登录常规检查.
      checkUserLogin(userEntity);
      loginSuccess(userEntity);

      JsonWebToken webToken = TokenUtil.createJsonWebToken(tenant, userEntity,
          userEntity.getStorage(), appId, openId, null);
      TokenEntity tokenEntity = this.passwordToken(webToken, appId, null);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(openId, tenant, true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(openId, tenant, ex.getMessage()));
      throw ex;
    }
  }

  @Override
  public TokenEntity createBindLogin(UserOpenCreateReq req) {
    try {
      // 查询数据库中是否有该手机号,有则绑定无则返回空
      UserEntity userEntity = userDao.findByUserName(req.getDirectoryId(), req.getMobilePhone());
      if (null == userEntity) {
        // 创建用户
        userEntity = new UserEntity(req.getDirectoryId(), req.getMobilePhone());
        userEntity.setNickName(req.getNickName());
        // 修改用户信息
        userEntity.setPhoneApproved(true);
        userEntity.setApproved(true);
        userDao.persist(userEntity);
      }
      // 用户登录常规检查.
      checkUserLogin(userEntity);
      // 绑定
      OpensEntity curOpen = new OpensEntity();
      curOpen.setOpenType(req.getOpenType());
      curOpen.setAppId(req.getAppid());
      curOpen.setOpenId(req.getOpenid());
      curOpen.setUnionId(req.getUnionid());
      curOpen.setUserId(userEntity.getUserId());
      opensDao.merge(curOpen);
      // 登陆成功
      // loginSuccess(userEntity);
      JsonWebToken webToken = TokenUtil.createJsonWebToken(req.getTenant(), userEntity,
          userEntity.getStorage(), req.getAppid(), req.getOpenid(), null);
      TokenEntity tokenEntity = this.passwordToken(webToken, req.getAppid(), null);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(req.getOpenid(), req.getTenant(), true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(req.getMobilePhone() + "/" + req.getOpenid(),
          req.getTenant(), ex.getMessage()));
      throw ex;
    }
  }

  @Override
  public boolean updateBindPhone(String mobilePhone) {
    // 当前用户信息.
    JsonWebToken token = GrpcContext.USER_TOKEN.get();
    String userId = token.getName();
    UserEntity userEntity = findById(UUIDUtil.fromString(userId));
    // 修改手机号.
    userEntity.setMobilePhone(mobilePhone);
    userDao.merge(userEntity);
    return true;
  }

  @Override
  public TokenEntity loginOrgan(String tenant) {
    JmashPrincipal principal = GrpcContext.getPrincipal();
    try {
      // 查询用户
      UserEntity user = userDao.findUserByUnifiedId(principal.getName());
      // 用户登录常规检查.
      checkUserLogin(user);
      userDao.refresh(user, LockModeType.PESSIMISTIC_WRITE);
      loginSuccess(user);
      String clientId = principal.getClientId();
      JsonWebToken webToken = TokenUtil.createJsonWebToken(tenant, user, user.getStorage(),
          clientId, principal.getSubject(), null);
      TokenEntity tokenEntity = this.passwordToken(webToken, clientId, null);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(principal.getSubject(), tenant, true));
      // 缓存源AccessToken.
      String originAccessToken = GrpcContext.USER_AUTH.get();
      OrganUserAccessToken.putOriginAccessToken(originAccessToken, principal,
          tokenEntity.getExpiresIn() - 300);
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(principal.getSubject(), tenant, ex.getMessage()));
      // 用户名或密码错误.
      if (ex.getCode() == 6) {
        loginFailed(ex.getAccountId(), SecretType.Login.name());
      }
      throw ex;
    }
  }


  @Override
  public UserEntity createOrganUser(UserEntity unifiedUser, OrganUserCreateReq req) {
    String unifiedId = UUIDUtil.uuid32(unifiedUser.getUserId());
    // 1.查询用户
    UserEntity user = userDao.findUserByUnifiedId(unifiedId);
    if (user != null) {
      return user;
    }
    // 2.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    user = UserMapper.INSTANCE.clone(unifiedUser);
    user.setUserId(UUID.randomUUID());
    user.setUnifiedId(unifiedId);
    if (StringUtils.isNotBlank(req.getRealName())) {
      user.setRealName(req.getRealName());
    }
    if (StringUtils.isNotBlank(req.getRealName())) {
      user.setNickName(req.getRealName());
    }
    if (StringUtils.isNotBlank(req.getDirectoryId())) {
      user.setDirectoryId(req.getDirectoryId());
    }
    if (StringUtils.isNotBlank(req.getAvatar())) {
      user.setAvatar(req.getAvatar());
    }
    userDao.persist(user);
    Set<String> roles = new HashSet<>();
    roles.addAll(req.getRoleCodesList());
    // 初始创建用户,创建组织管理员
    if (req.getCreator()) {
      roles.add("organ");
      roles.add("cms");
    }
    roleWrite.assignUser(user.getUserId(), roles);
    // 部门岗位设置
    if (req.getDeptIdCount() > 0) {
      RoleEntity role = this.roleDao.findByCode("member");
      if (role != null) {
        userJobsDao.addUserDeptJob(user.getUserId(), req.getDeptIdList(), role.getRoleId());
      }
    }
    return user;
  }

  @Override
  public UserEntity createOrganUser(OrganUserCreateReq req) {
    UserEntity user = UserMapper.INSTANCE.create(req);
    // 2.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    user.setUserId(UUID.randomUUID());
    user.setUnifiedId(UUIDUtil.emptyUUID32());
    user.setNickName(req.getRealName());
    user.setPhoneApproved(true);
    user.setApproved(true);
    userDao.persist(user);
    Set<String> roles = new HashSet<>();
    roles.addAll(req.getRoleCodesList());
    // 初始创建用户,创建组织管理员
    if (req.getCreator()) {
      roles.add("organ");
      roles.add("cms");
    }
    roleWrite.assignUser(user.getUserId(), roles);
    // 部门岗位设置
    if (req.getDeptIdCount() > 0) {
      RoleEntity role = this.roleDao.findByCode("member");
      if (role != null) {
        userJobsDao.addUserDeptJob(user.getUserId(), req.getDeptIdList(), role.getRoleId());
      }
    }
    return user;
  }

  /**
   * 创建组织系统用户.
   */
  @Override
  public UserEntity createOrganSystemUser() {
    JmashPrincipal principal = GrpcContext.getPrincipal();
    Subject subject = (Subject) GrpcContext.USER_SUBJECT.get();
    // 识别系统用户和运维人员.
    if (!(principal.getSubject().equals("jmash") || principal.getSubject().equals("admin")
        || subject.hasRole("tester"))) {
      return null;
    }
    // 查询用户
    UserEntity user = userDao.findUserByUnifiedId(principal.getName());
    if (null != user) {
      return user;
    }
    // 创建用户.
    user = new UserEntity();
    user.setUserId(UUID.randomUUID());
    user.setUnifiedId(principal.getName());
    user.setLoginName(principal.getSubject());
    user.setRealName("");
    user.setNickName(principal.getSubject());
    user.setDirectoryId("jmash");
    user.setStorage(principal.getStorage());
    user.setApproved(true);
    user.setStatus(UserStatus.enabled);
    userDao.persist(user);
    // 创建组织系统管理员
    Set<String> roles = new HashSet<>();
    roles.add("organ");
    roles.add("cms");
    if (principal.getSubject().equals("admin")) {
      roles.add("admin");
    }
    if (principal.getSubject().equals("jmash")) {
      roles.add("system");
    }
    if (subject.hasRole("tester")) {
      roles.add("tester");
      user.setNickName("系统维护员");
    }
    roleWrite.assignUser(user.getUserId(), roles);
    return user;
  }

  @Override
  public TokenEntity createUserByQrcode(ScanCodeCreateUserReq req) {
    // 检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }

    // 校验是否已经扫码.
    LoginQrcodeCheckReq.Builder request =
        LoginQrcodeCheckReq.newBuilder().setTenant(req.getTenant());
    request.setAuthorizerAppid(req.getAuthorizerAppid()).setTicket(req.getTicket());
    xyvcard.wechat.protobuf.UserModel model =
        WechatClient.getWechatBlockingStub().loginQrcodeCheck(request.build());
    if (StringUtils.isBlank(model.getOpenId())) {
      return null;
    }

    try {
      // 判断验证码是否正确.
      if (!VerifyCodeUtil.validate(req.getMobilePhone(), req.getValidCode())) {
        throw new JmashAuthenticationException(6, "验证码输入不正确,请重新获取!");
      }
      // 查询数据库中是否有该手机号,有则绑定无则返回空
      UserEntity userEntity = userDao.findByUserName(req.getDirectoryId(), req.getMobilePhone());
      if (null == userEntity) {
        // 创建用户
        userEntity = new UserEntity(req.getDirectoryId(), req.getMobilePhone());
        userEntity.setNickName("新用户");
        // 修改用户信息
        userEntity.setPhoneApproved(true);
        userEntity.setApproved(true);
        userDao.persist(userEntity);
      }
      // 绑定
      OpensEntity curOpen = new OpensEntity();
      curOpen.setOpenType(OpensType.wechat);
      curOpen.setAppId(req.getAuthorizerAppid());
      curOpen.setOpenId(model.getOpenId());
      curOpen.setUnionId(model.getUnionId());
      curOpen.setUserId(userEntity.getUserId());
      opensDao.merge(curOpen);
      // 用户登录常规检查.
      checkUserLogin(userEntity);

      JsonWebToken webToken = TokenUtil.createJsonWebToken(req.getTenant(), userEntity,
          userEntity.getStorage(), req.getAuthorizerAppid(), model.getOpenId(), null);
      TokenEntity tokenEntity = this.passwordToken(webToken, req.getAuthorizerAppid(), null);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(model.getOpenId(), req.getTenant(), true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(req.getMobilePhone() + "/" + model.getOpenId(),
          req.getTenant(), ex.getMessage()));
      throw ex;
    }
  }

  @Override
  public TokenEntity registerUser(RegisterUserReq req) {
    // 检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    try {
      // 判断验证码是否正确.
      if (!VerifyCodeUtil.validate(req.getName(), req.getValidCode())) {
        throw new JmashAuthenticationException(6, "验证码输入不正确,请重新获取!");
      }
      // 判断2次密码是否一致.
      if (!req.getPwd().equals(req.getRepeatPwd())) {
        throw new JmashAuthenticationException(6, "两次密码输入不一致!");
      }
      // 查询数据库中是否有该账号,有则绑定无则返回空
      UserEntity userEntity = userDao.findByUserName(req.getDirectoryId(), req.getName());
      if (null == userEntity) {
        // 创建用户
        userEntity = new UserEntity();
        userEntity.setDirectoryId(req.getDirectoryId());
        if (req.getName().indexOf("@") != -1) {
          userEntity.setEmail(req.getName());
        } else if (StringUtils.isNumeric(req.getName())) {
          userEntity.setMobilePhone(req.getName());
        } else {
          userEntity.setLoginName(req.getName());
        }
        userEntity.setNickName(req.getNickName());
        if (StringUtils.isBlank(req.getRealName())) {
          userEntity.setRealName(req.getNickName());
        }
        // 修改用户信息
        userEntity.setPhoneApproved(true);
        userEntity.setApproved(true);
        userDao.persist(userEntity);
      }
      // 设置密码.
      UserSecretEntity secret = new UserSecretEntity();
      secret.setUserId(userEntity.getUserId());
      secret.setSecretType(SecretType.Login.name());
      secret.setPwdAlt(UUIDUtil.uuid32(UUID.randomUUID()));
      String pwdValue =
          PwdHashUtil.encrypt(secret.getPwdFormat(), req.getPwd(), secret.getPwdAlt());
      secret.setPwdValue(pwdValue);
      userSecretDao.persist(secret);
      // 用户登录常规检查.
      checkUserLogin(userEntity);
      JsonWebToken webToken = TokenUtil.createJsonWebToken(req.getTenant(), userEntity,
          userEntity.getStorage(), req.getClientId(), req.getName(), null);
      TokenEntity tokenEntity = this.passwordToken(webToken, req.getClientId(), null);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(req.getName(), req.getTenant(), true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(
          SafeEvent.loginFail(req.getName(), req.getTenant(), ex.getMessage()));
      throw ex;
    }
  }

  @Override
  public boolean forgotPwd(ForgotPwdReq req) {
    // 检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    // 判断验证码是否正确.
    if (!VerifyCodeUtil.validate(req.getName(), req.getValidCode())) {
      throw new JmashAuthenticationException(6, "验证码输入不正确,请重新获取!");
    }
    // 判断2次密码是否一致.
    if (!req.getPwd().equals(req.getRepeatPwd())) {
      throw new JmashAuthenticationException(6, "两次密码输入不一致!");
    }
    // 查询数据库中是否有该账号.
    UserEntity userEntity = userDao.findByUserName(req.getDirectoryId(), req.getName());
    if (null == userEntity) {
      throw new JmashAuthenticationException(6, "用户不存在!");
    }
    // 设置密码.
    UserSecretEntity secret = new UserSecretEntity();
    secret.setUserId(userEntity.getUserId());
    secret.setSecretType(SecretType.Login.name());
    secret.setPwdAlt(UUIDUtil.uuid32(UUID.randomUUID()));
    String pwdValue =
        PwdHashUtil.encrypt(secret.getPwdFormat(), req.getPwd(), secret.getPwdAlt());
    secret.setPwdValue(pwdValue);
    userSecretDao.persist(secret);
    return true;
  }

  @Override
  public TokenEntity createUserByGzh(String tenant, String name, String directoryId, String appId,
      OauthUserInfoResp resp) {
    try {
      // 尝试union登录.
      TokenEntity token = loginByOpenId(tenant, OpensType.wechat, appId, resp.getOpenId(),
          resp.getUnionId(), false, null);
      if (token != null) {
        return token;
      }
      // 查询数据库中是否有该手机号,有则绑定无则返回空
      UserEntity userEntity = null;
      if (StringUtils.isNotBlank(name)) {
        userEntity = userDao.findByUserName(directoryId, name);
      }
      if (null == userEntity) {
        // 创建用户
        userEntity = new UserEntity();
        userEntity.setDirectoryId(directoryId);
        userEntity.setNickName(resp.getNickname());
        if (StringUtils.isNotBlank(name) && name.contains("@")) {
          userEntity.setEmail(name);
          userEntity.setEmailApproved(true);
        } else if (StringUtils.isNotBlank(name)) {
          userEntity.setMobilePhone(name);
          userEntity.setPhoneApproved(true);
        } else {
          userEntity.setLoginName(resp.getOpenId());
        }
        if (resp.getSex() == 1) {
          userEntity.setGender(Gender.male);
        } else if (resp.getSex() == 2) {
          userEntity.setGender(Gender.female);
        }
        userEntity.setAvatar(resp.getHeadimgurl());
        // 修改用户信息
        userEntity.setApproved(true);
        userDao.persist(userEntity);
      }
      // 用户登录常规检查.
      checkUserLogin(userEntity);
      // 绑定
      OpensEntity curOpen = new OpensEntity();
      curOpen.setOpenType(OpensType.wechat);
      curOpen.setAppId(appId);
      curOpen.setNickName(resp.getNickname());
      curOpen.setOpenId(resp.getOpenId());
      curOpen.setUnionId(resp.getUnionId());
      curOpen.setUserId(userEntity.getUserId());
      opensDao.merge(curOpen);
      // 登陆成功
      // loginSuccess(userEntity);
      JsonWebToken webToken = TokenUtil.createJsonWebToken(tenant, userEntity,
          userEntity.getStorage(), appId, resp.getOpenId(), null);
      TokenEntity tokenEntity = this.passwordToken(webToken, appId, null);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(resp.getOpenId(), tenant, true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(appId + "/" + resp.getOpenId(), tenant, ex.getMessage()));
      throw ex;
    }
  }

  @Override
  public TokenEntity createUserByAlipay(String tenant, String name, String directoryId,
      String appId, AlipaySystemOauthTokenResponse resp, AlipayUserInfoShareResponse userInfo) {
    String openId = StringUtils.isNotBlank(resp.getUserId()) ? resp.getUserId() : resp.getOpenId();
    try {
      // 查询数据库中是否有该手机号,有则绑定无则返回空
      UserEntity userEntity = null;
      if (StringUtils.isNotBlank(name)) {
        userEntity = userDao.findByUserName(directoryId, name);
      }
      if (null == userEntity) {
        // 创建用户
        userEntity = new UserEntity();
        userEntity.setDirectoryId(directoryId);
        userEntity.setNickName(userInfo.getNickName());
        if (StringUtils.isNotBlank(userInfo.getEmail()) && userInfo.getEmail().contains("@")) {
          userEntity.setEmail(userInfo.getEmail());
          userEntity.setEmailApproved(true);
        } else if (StringUtils.isNotBlank(userInfo.getMobile())) {
          userEntity.setMobilePhone(userInfo.getMobile());
          userEntity.setPhoneApproved(true);
        } else {
          userEntity.setLoginName(openId);
        }
        if (StringUtils.equals("M", userInfo.getGender())) {
          userEntity.setGender(Gender.male);
        } else if (StringUtils.equals("F", userInfo.getGender())) {
          userEntity.setGender(Gender.female);
        }
        userEntity.setAvatar(userInfo.getAvatar());
        // 修改用户信息
        userEntity.setApproved(true);
        userDao.persist(userEntity);
      }
      // 用户登录常规检查.
      checkUserLogin(userEntity);
      // 绑定
      OpensEntity curOpen = new OpensEntity();
      curOpen.setOpenType(OpensType.ali_pay);
      curOpen.setAppId(appId);
      curOpen.setNickName(userInfo.getNickName());
      curOpen.setOpenId(openId);
      curOpen.setUnionId(resp.getUnionId());
      curOpen.setUserId(userEntity.getUserId());
      opensDao.merge(curOpen);
      // 登陆成功
      // loginSuccess(userEntity);
      JsonWebToken webToken = TokenUtil.createJsonWebToken(tenant, userEntity,
          userEntity.getStorage(), appId, openId, null);
      TokenEntity tokenEntity = this.passwordToken(webToken, appId, null);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(openId, tenant, true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(appId + "/" + openId, tenant, ex.getMessage()));
      throw ex;
    }
  }

  @Override
  public TokenEntity createUserByUnionPay(String tenant, String directoryId, String appId,
      UnionUserMobile resp) {
    try {
      // 查询数据库中是否有该手机号,有则绑定无则返回空
      UserEntity userEntity = userDao.findByUserName(directoryId, resp.getMobile());
      if (null == userEntity) {
        // 创建用户
        userEntity = new UserEntity();
        userEntity.setDirectoryId(directoryId);
        userEntity.setNickName("云闪付" + resp.getMobile().substring(resp.getMobile().length() - 4));
        userEntity.setMobilePhone(resp.getMobile());
        userEntity.setPhoneApproved(true);
        // 修改用户信息
        userEntity.setApproved(true);
        userDao.persist(userEntity);
      }
      // 用户登录常规检查.
      checkUserLogin(userEntity);
      // 绑定
      OpensEntity curOpen = new OpensEntity();
      curOpen.setOpenType(OpensType.union_pay);
      curOpen.setAppId(appId);
      curOpen.setOpenId(resp.getOpenId());
      curOpen.setUnionId(resp.getUnionId());
      curOpen.setUserId(userEntity.getUserId());
      opensDao.merge(curOpen);
      // 登陆成功
      // loginSuccess(userEntity);
      JsonWebToken webToken = TokenUtil.createJsonWebToken(tenant, userEntity,
          userEntity.getStorage(), appId, resp.getOpenId(), null);
      TokenEntity tokenEntity = this.passwordToken(webToken, appId, null);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(resp.getOpenId(), tenant, true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(appId + "/" + resp.getOpenId(), tenant, ex.getMessage()));
      throw ex;
    }
  }

  @Override
  public TokenEntity loginApp(LoginAppReq req) {
    try {
      String scope = "api";
      String directoryId = "app";
      String clientId = req.getAppId();
      // 验证用户名和密码
      UserEntity user = loginByUserName(directoryId, req.getAppId(), req.getAppSecret());
      userDao.refresh(user, LockModeType.PESSIMISTIC_WRITE);
      loginSuccess(user);
      JsonWebToken webToken = TokenUtil.createJsonWebToken(req.getTenant(), user, user.getStorage(),
          clientId, req.getAppId(), scope);
      TokenEntity tokenEntity = this.passwordToken(webToken, clientId, scope);
      // 登录事件
      WebContext.USER_TOKEN.set(webToken);
      event.fireAsync(SafeEvent.login(req.getAppId(), req.getTenant(), true));
      return tokenEntity;
    } catch (JmashAuthenticationException ex) {
      event.fireAsync(SafeEvent.loginFail(req.getAppId(), req.getTenant(), ex.getMessage()));
      return null;
    }
  }
}
